#include <cstdio>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <pthread.h>
#include <map>
#include <list>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <errno.h>

#define MAX_SIZE 1024
#define LOGINOK 00
#define LOGINNOK 01
#define MESSAGESENDOK 02
#define MESSAGESAVEDFORFUTURE 03
#define MESSAGEDROPPED 04
#define CODESIZE 2
#define SEQNOSIZE 8

using namespace std;

int sendSeq = 1;
map<string, list<string> > waitingMessages;

vector<string> knownUsers;
map<string, FILE*> loggedUsers;

vector<string> &split(const string &s, char delim, vector<string> &elems) {
	stringstream ss(s);
	string item;
	while (getline(ss, item, delim)) {
		elems.push_back(item);
	}
	return elems;
}

vector<string> split(const string &s, char delim) {
	vector<string> elems;
	return split(s, delim, elems);
}

string intToHex(int i) {
	stringstream stream;
	stream << setfill('0') << setw(sizeof(int) * 2) << hex << i;
	return stream.str();
}

string addPadding(int i, int size) {
	stringstream stream;
	stream << setfill('0') << setw(size) << i;
	return stream.str();
}

int hexToInt(string i) {

	int out;

	stringstream stream;

	stream << hex << i;
	stream >> out;

	//	cout << out << flush;

	return out;
}

void sendMessage(int code, string msg, FILE *fp) {
	stringstream message;

	message << addPadding(sendSeq++, SEQNOSIZE) << addPadding(code, 2) << msg;

	string msg1 = message.str();
	string length = intToHex(msg1.size());

	string finalMessage = length + msg1;

	cout << "Sending message: " << finalMessage << endl << flush;

	int a = fputs(finalMessage.c_str(), fp);
	fflush(fp);

	cout << "ERROR: " << strerror(errno) << endl << flush;

	cout << "message sent" << endl << flush;
}

void *oneClient(void *arg) {
	string username;

	FILE *fp = (FILE*) arg;

	int messageCode;
	int seqNo;
	int i = 0;

	//SELECT
	fd_set readset;
	FD_ZERO(&readset);
	FD_SET(fileno(fp),&readset);

	fd_set writeset;
	FD_ZERO(&writeset);
	FD_SET(fileno(fp),&writeset);

	while (true) {
		bool writeable = false;
		if (i > 100)
			break;
		i++;

		cout << "waiting..." << endl << flush;

		int ret = select(fileno(fp) + 1, &readset, NULL, NULL, NULL);
		cout << "Continue..." << endl << flush;
		if (FD_ISSET(fileno(fp), &readset)) {
			if (FD_ISSET(fileno(fp), &writeset)) {
				writeable = true;
			}
			//ok cteme
			cout << "Cteni ready" << endl << flush;

			cout << "Pred sizeBufferem" << endl << flush;
			//nacteme velikost zpravy
			char sizeBuffer[9];
			fgets(sizeBuffer, sizeof(sizeBuffer), fp);
			int size = hexToInt(sizeBuffer);

			cout << "Za sizeBufferem" << endl << flush;

			//nacteme seqNo
			char seqNoBuffer[9];
			fgets(seqNoBuffer, sizeof(seqNoBuffer), fp);
			seqNo = atoi(seqNoBuffer);
			cout << "Received seqNo: " << seqNo << endl << flush;

			//nacteme kod zpravy - 01, 02, 03...
			char codeBuffer[CODESIZE + 1];
			fgets(codeBuffer, sizeof(codeBuffer), fp);
			messageCode = atoi(codeBuffer);

			cout << "code: " << messageCode << endl << flush;
			//		cout << "Received message: " <<

			//precteme payload
			char messageBuffer[(size - CODESIZE - SEQNOSIZE) + 1];
			fgets(messageBuffer, sizeof(messageBuffer), fp);
			string message = messageBuffer;




			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//	Po odkomentovani nasledujici radku to prestane fungovat - nelze nic poslat ani prijmout. Netusim proc :/
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


//			sendMessage(10, "aaa", fp);
			fflush(fp);

			//----------------------------- user login ------------------------------
			if (messageCode == 1) {

				username = message;

				cout << "Login : " << username << endl << flush;

				if (loggedUsers.count(username) == 0) {
					//pokud uzivatel jeste neni prihlasen, tak ho pridame
					loggedUsers[username] = fp;
					sendMessage(1, "", fp);
				} else {
					//pokud uz prihlaseny je, vratime chybu a zabijem vlakno
					sendMessage(6, "This user is already logged in! Get OUT!",
							fp);
					break;
				}

				//zname toho uzivatele?
				if (find(knownUsers.begin(), knownUsers.end(), username)
						== knownUsers.end()) {
					//pridame uzivatele do znamych uzivatelu
					knownUsers.push_back(username);
				}
			}
			//-----------------------------------------------------------

			//----------------------------- user logout ------------------------------
			if (messageCode == 2) {
				loggedUsers.erase(username);
				break;
			}
			//-----------------------------------------------------------

			//----------------------------- get new messages ------------------------------
			if (messageCode == 3) {

				if (waitingMessages.count(username) == 0) {
					sendMessage(7, "", fp);
				} else {
					map<string, list<string> >::iterator it;
					it = waitingMessages.find(username);
					list<string> msgs = it->second;

					list<string>::iterator it1;
					for (it1 = msgs.begin(); it1 != msgs.end(); it1++) {
						string s = it1->c_str();
						sendMessage(8, s, fp);
					}

					waitingMessages.erase(username);
					sendMessage(7, "", fp);
				}

			}
			//-----------------------------------------------------------

			//----------------------------- send message ------------------------------
			if (messageCode == 4) {
				string msg1 = message;

				vector<string> x = split(msg1, ':');
				string receiver = x[0];
				string msg = x[1];

				//prijemce neni a nikdy nebyl prihlasen
				if (find(knownUsers.begin(), knownUsers.end(), receiver)
						== knownUsers.end()) {
					sendMessage(5, "User doesnt exist.", fp);
				} else {
					//prijemce EXISTUJE

					//prijemce je prihlaseny, muzeme rovnou dorucit zpravu
					if (loggedUsers.count(receiver) != 0) {
						FILE* f = (FILE*) loggedUsers[receiver];

						//posleme zpravu prijemci
						sendMessage(9, msg, f);
						fflush(f);

						//odesilateli posleme potvrzeni
						sendMessage(3, "Message sent.", fp);
						fflush(fp);

					} else {
						//prijemce neni prihlasen, zpravu ulozime
						list<string> list = waitingMessages[receiver];
						list.push_back(msg);
						waitingMessages[receiver] = list;

						sendMessage(4, "Message queued.", fp);
					}

				}

			}
			cout << "Cisteni..." << endl << endl;
			//-----------------------------------------------------------
			memset(sizeBuffer, NULL, sizeof(sizeBuffer));
			memset(codeBuffer, NULL, sizeof(codeBuffer));
			memset(seqNoBuffer, NULL, sizeof(seqNoBuffer));
			memset(messageBuffer, NULL, sizeof(messageBuffer));
			messageCode = -1;
			fflush(fp);
		}
	}

	fclose(fp);
	return 0;
}

int main(int argc, char* argv[]) {

	string port = argv[1];
	int port1 = atoi(port.c_str());

	int sock_descriptor, conn_desc;
	struct sockaddr_in serv_addr, client_addr;
	char buff[MAX_SIZE];

	//pouzijem TCP
	sock_descriptor = socket(AF_INET, SOCK_STREAM, 0);
	if (sock_descriptor < 0)
		printf("Failed creating socket\n");
	bzero((char *) &serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;

	//poslouchame na vsech adresach
	serv_addr.sin_addr.s_addr = INADDR_ANY;

	//port
	serv_addr.sin_port = htons(port1);

	//prirazeni portu
	if (bind(sock_descriptor, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
			< 0)
		printf("Failed to bind\n");

	//max pet cekajicich spojeni
	listen(sock_descriptor, 5);

	while (true) {

		int size = sizeof(client_addr);

		pthread_t thread;

		int con;

		con = accept(sock_descriptor, (struct sockaddr *) &client_addr,
				(socklen_t*) &size);
		FILE *file;
		file = fdopen(con, "r+");
		cout << "A: " << con << endl << flush;
		pthread_create(&thread, 0, oneClient, file);
		pthread_detach(thread);

	} //endWhile

	close(sock_descriptor);

	return 0;

}

